Repository navigation
fix(ghosts): supersede Codex ghosts by provider item id - #1383
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Codex ghost is keyed by the proxy response id (resp_...), while the committed rollout entry only carried the rollout turn UUID (codexTurnId). reconcileUpstream compared the two, which can never match. Text blocks have no tool id to fall back on, so every Codex text ghost orphaned after 30 s and stayed hidden only by render rules 3 and 4. Both sides do carry the provider item id (msg_/rs_/fc_/ctc_): the live block has it from the proxy adapter, and the rollout has it on every response_item. On six real 0.157.1 sessions, all 45 msg_ ids seen on the proxy were present verbatim in their rollouts. The mapper now stamps codexItemId, the ghost records its block's itemId in its context, and reconcileUpstream matches on it. The dead turn-id branch and the stale comments claiming the mapper stamped the response id are gone. Fail-first: a real recorded message (proxy SSE frames plus the rollout record) driven through the real adapter, fold, mapper and reconciler. Red on main; each half of the fix is pinned by mutation. Fixes #1231 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… match Review a of #1383: rollout.ts, ghosts.ts and useIpcSubscriptions.ts still said Codex ghosts reconcile by (turnId, blockIndex) or by turn id. They reconcile by provider item id now, and codexTurnId's remaining consumer is renderUnits.ts. Refs #1231 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ot matched Review c of #1383: the header listed rs_ ids as matched, but reasoning items map to no feed entry, so reasoning ghosts still orphan as before. The identity test now maps through createCodexTranscriptEntryMapper, the wrapper all three production ingest surfaces use. Refs #1231 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
Author
|
Disposition (W1). Three reviews: codex a at
The commits after the reviewed heads change comments and a test only. Exact-head CI is pending at |
Owner
Author
|
READY (W1). All three reviews MERGE-READY at |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1231.
Plan:
docs/plans/2026-09-27-codex-ghost-item-identity.md(first commit).Problem
Codex text ghosts were never superseded.
resp_…).codexTurnId, the rollout'sturn_contextUUID.reconcileUpstreamcompared the two, which can never be equal.So every Codex text ghost orphaned after 30 s. It stayed hidden only by render rules 3 and 4 in
selectMergedEntries, as a latent identity weakness (P3 after #1235).Root cause and evidence
The only id the live stream and the rollout share is the provider item id (
msg_…,rs_…,fc_…,ctc_…):SemanticLiveBlock.itemId);response_item.On six real Agent Code codex-cli 0.157.1 sessions (proxy dumps against their rollouts, 2026-09-27), all 45
msg_…ids seen on the proxy stream were present verbatim in their rollouts.Change
mapCodexRolloutToFeedEntriesstampscodexItemId(the item'spayload.id) on every entry it maps from aresponse_item(stampCodexItemId).ghostContextForBlockrecords the block'sitemIdin the ghost'scontext, atp's free-form slot. No package change.reconcileUpstreamsupersedes a ghost whencontext.itemId === entry.codexItemId.codexTurnId === ghost.turnIdbranch compared a rollout UUID with a response id and could never be true. The stale comments claiming the mapper stamped the response id are corrected inghosts.ts,rollout.tsandFeed.tsx.codexTurnIdstamping stays: the feed's committed-text keys (renderUnits.ts) use it.Tests
ghosts.codexItemIdentity.test.tsuses one real 0.157.1 assistant message seen from both sides (testing/fixtures/ghosts/codex/message-item-identity.json): the proxy SSE frames and the rollout record for the samemsg_…, verbatim. The text is a one-line reviewer commentary.The chain, all real code:
CodexResponsesAdapter(codex-headless adapter harness) and the realfoldSemanticEvent.ghostsFromSemanticTurn.reconcileUpstream.The ghost must be superseded by that entry. A message with a different id must not supersede it.
Fail-first. Red on main, where the ghost is not superseded. Each half of the fix is pinned by mutation: dropping the ghost's
itemId, or the mapper stamp, turns it red.Verification
npx tsc -bclean (testing/fixtures/ghosts/**/*.jsonadded totsconfig.web.json).src/renderer/src/session-runtime,src/renderer/src/features/feed,src/providers/codexandsrc/renderer/src/workspace: 235 files, 1,721 tests.Out of scope
token_usage_recordroute ({turn_id, response_id}after each response). The item id is exact per block and needs no lookahead; parser#38 tracks the record type itself.fc_…,ctc_…) get the same item-id match, and still also pair bytool_use_id/call_id.response_itemmaps to no feed entry, so a reasoning ghost (rs_…) has nothing to be superseded by. It still orphans after 30 s and stays hidden by the same render rules as before.2609c3ac.🤖 Generated with Claude Code